//**************************************************
// Written by Nyi Nyi Than - nyinyithann@gmail.com
// Copyright (c) 2010
// License: The MIT License (MIT)
//**************************************************

using System;
using System.Collections.Generic;
using System.Collections;

namespace Tanker
{
    public static class Sequence
    {
        /// <summary>
        /// Generates a new sequence based on the collection passed.
        /// </summary>
        /// <typeparam name="TElement">The type of elements of the result sequence.</typeparam>
        /// <param name="source">The input collection.</param>
        /// <returns>The result sequence.</returns>
        /// <exception cref="ArgumentNullException">Thrown when source is null.</exception>
        public static Sequence<TElement> Create<TElement>(IEnumerable<TElement> source)
        {
            Guard.ArgumentNotNull(source, "source");
            return new Sequence<TElement>(source);
        }

        /// <summary>
        /// Generates an empty sequence if the source is null, otherwise, a new sequence based on the collection passed.
        /// </summary>
        /// <typeparam name="TElement">The type of elements of the result sequence.</typeparam>
        /// <param name="source">The input collection.</param>
        /// <returns>The result sequence.</returns>
        public static Sequence<TElement> CreateEmptyIfNull<TElement>(IEnumerable<TElement> source)
        {
            return source == null
                    ? Empty<TElement>()
                    : new Sequence<TElement>(source);
        }

        /// <summary>
        /// Generates a Sequence<typeparam name="TElement"/> based on the collection passed.
        /// Elements which are not of type TElement will not be included.
        /// </summary>      
        /// <param name="source">The input collection.</param>
        /// <returns>The result sequence.</returns>
        /// <exception cref="ArgumentNullException">Thrown when source is null.</exception>
        public static Sequence<TElement> OfType<TElement>(IEnumerable source)
        {
            Guard.ArgumentNotNull(source, "source");
            return new Sequence<TElement>(OfTypeInternal<TElement>(source));
        }

        private static IEnumerable<TElement> OfTypeInternal<TElement>(IEnumerable source)
        {
            foreach (object item in source)
            {
                if (item is TElement)
                {
                    yield return (TElement)item;
                }
            }
        }

        /// <summary>
        /// Converts the elements of an System.Collections.IEnumerable to the specified type.
        /// </summary>
        /// <typeparam name="TElement">The type to convert the elements of source to.</typeparam>
        /// <param name="source">The System.Collections.IEnumerable that contains the elements to be converted.</param>
        /// <returns>An <see cref="Sequence{TElement}"/> that contains each element of the source collection converted to the specified type.</returns>
        /// <exception cref="ArgumentNullException">Thrown when source is null.</exception>
        /// <exception cref="InvalidCastException">Thrown when an element in the sequence cannot be cast to type TElement.</exception>
        public static Sequence<TElement> Cast<TElement>(IEnumerable source)
        {
            Guard.ArgumentNotNull(source, "source");
            return new Sequence<TElement>(CastInternal<TElement>(source));
        }

        private static IEnumerable<TElement> CastInternal<TElement>(IEnumerable source)
        {
            foreach (object item in source)
            {
                yield return (TElement)item;
            }
        }

        /// <summary>
        /// Generates a new sequence based on the result of generator function passed.
        /// </summary>
        /// <typeparam name="TElement">The type of elements of the result sequence.</typeparam>
        /// <param name="generator">A function that generates items for the result sequence.</param>
        /// <returns>The result sequence.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the generator function is null.</exception>
        public static Sequence<TElement> Create<TElement>(Func<IEnumerable<TElement>> generator)
        {
            Guard.ArgumentNotNull(generator, "generator");
            return new Sequence<TElement>(generator());
        }

        /// <summary>
        /// Generates a new sequence which, when iterated, will return successive
        /// elements by calling the given function, up to the given count. 
        /// </summary>
        /// <typeparam name="TElement">The type of elements of the result sequence.</typeparam>
        /// <param name="count">The maximum number of items to generate for the sequence.</param>
        /// <param name="generator">A function that generates an item in the sequence from a given index.</param>
        /// <returns>The result sequence.</returns>
        /// <exception cref="ArgumentException">Thrown when count is negative.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the generator function is null.</exception>
        public static Sequence<TElement> Create<TElement>(int count, Func<int, TElement> generator)
        {
            Guard.ArgumentNotNegative(count, "count");
            Guard.ArgumentNotNull(generator, "generator");

            return new Sequence<TElement>(CreateInternal(count, generator));
        }

        private static IEnumerable<TElement> CreateInternal<TElement>(int count, Func<int, TElement> generator)
        {
            for (int i = 0; i < count; i++)
            {
                yield return generator(i);
            }
        }

        /// <summary>Creates an empty sequence.</summary>        
        /// <returns>The result empty sequence.</returns>
        public static Sequence<TElement> Empty<TElement>()
        {
            return new Sequence<TElement>(new TElement[0]);
        }

        /// <summary>
        /// Generates a sequence that contains only one item.
        /// </summary>
        /// <typeparam name="TElement">The type of the element.</typeparam>
        /// <param name="element">The input element.</param>
        /// <returns>The result sequence.</returns>
        public static Sequence<TElement> Singleton<TElement>(TElement element)
        {
            Guard.ArgumentNotNull(element, "TElement");
            return new Sequence<TElement>(new TElement[] { element });
        }

        /// <summary>
        /// Generates a sequence of integral numbers within a specified range.
        /// </summary>
        /// <param name="start">The value of the first integer in the sequence.</param>
        /// <param name="count"> The number of sequential integers to generate.</param>
        /// <returns>The result sequence that contains a range of sequential integral numbers.</returns>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when count is less than 0 or (start + count -1) is larger than System.Int32.MaxValue</exception>
        public static Sequence<int> Range(int start, int count)
        {
            long num = (start + count) - 1L;
            if ((count < 0) || (num > 0x7fffffffL))
            {
                throw new ArgumentOutOfRangeException("count");
            }

            return new Sequence<int>(RangeInternal(start, count));
        }

        private static IEnumerable<int> RangeInternal(int start, int count)
        {
            for (int i = 0; i < count; i++)
            {
                yield return start++;
            }
        }

        /// <summary>
        /// Generates a sequence that contains one repeated value.
        /// </summary>
        /// <param name="element">The value to be repeated.</param>
        /// <param name="count">The number of times to repeat the value in the generated sequence.</param>
        /// <returns>The result sequence that contains a repeated value.</returns>
        /// <exception cref="ArgumentException">Thrown when count is less than 0.</exception>
        public static Sequence<TElement> Repeat<TElement>(TElement element, int count)
        {
            Guard.ArgumentNotNegative(count, "count");

            return new Sequence<TElement>(RepeatInternal(element, count));
        }

        private static IEnumerable<TElement> RepeatInternal<TElement>(TElement element, int count)
        {
            for (int i = 0; i < count; i++)
            {
                yield return element;
            }
        }
    }
}
